const _ext3 = 0x3d;
let Base64_MAP: number[];
let Base64_MAP_map: Map<number, number> = new Map<number, number>();

function MakeMap(textmap: string) {
  let map = new Array(1 << 6);
  if (textmap.length != map.length) {
    let i;
    for (i = 0; i < 26; i++) map[i] = (65 + i) & 0xff;
    for (i = 0; i < 26; i++) map[26 + i] = (97 + i) & 0xff;
    for (i = 0; i < 10; i++) map[52 + i] = (48 + i) & 0xff;
    map[62] = 43;
    map[63] = 47;
  } else for (var i = 0; i < map.length; i++) map[i] = textmap[i].charCodeAt(0) & 0xff;

  for (var i = 0; i < map.length; i++) Base64_MAP_map[map[i]] = i & 0xff;
  Base64_MAP_map[_ext3] = 0;
  Base64_MAP = map;
}
MakeMap("");
const MinBytes = 3;
const OutBytes = 4;

export class Base64 {
  public BufLEN: number;
  public Buffer: number[];
  public get DecodeMaxLength(): number {
    return this.BufLEN / MinBytes * OutBytes;
  }
  public get EncodeMaxLength(): number {
    return this.BufLEN / OutBytes * MinBytes;
  }
  public constructor(buflen: number) {

    this.BufLEN = buflen + (OutBytes * MinBytes - buflen % (OutBytes * MinBytes));
    this.Buffer = new Array(this.BufLEN);
  }
  public Decode(byteInput: number[], start: number, length: number) {
    if (length + start > byteInput.length) length = byteInput.length - start;
    if (length == 0) return 0;
    if (length > this.DecodeMaxLength) throw "error";
    let i, j;
    let a, b, c, d;
    length += start;
    for (i = start, j = 0; i < length; i += OutBytes, j += MinBytes) {
      a = Base64_MAP_map[byteInput[i]];
      b = Base64_MAP_map[byteInput[i + 1]];
      c = Base64_MAP_map[byteInput[i + 2]];
      d = Base64_MAP_map[byteInput[i + 3]];

      this.Buffer[j] = (a << 2 | b >>> 4) & 0xff;
      this.Buffer[j + 1] = (b << 4 | c >>> 2) & 0xff;
      this.Buffer[j + 2] = (c << 6 | d) & 0xff;
    }
    if (byteInput[i - 1] == _ext3) j--;
    if (byteInput[i - 2] == _ext3) j--;
    return j;
  }
  public Encode(byteInput: number[], start: number, length: number) {

    if (length + start > byteInput.length) length = byteInput.length - start;
    if (length == 0) return 0;
    //超出缓存大小
    if (length > this.EncodeMaxLength) throw "error";
    let i, j;

    let len = length - length % MinBytes + start;
    length += start;
    for (i = start, j = 0; i < len; i += MinBytes, j += OutBytes) {
      this.Buffer[j] = Base64_MAP[(byteInput[i] >> 2) & 0b111111];
      this.Buffer[j + 1] = Base64_MAP[((byteInput[i] << 4) & 0b110000) | ((byteInput[i + 1] >> 4) & 0b1111)];
      this.Buffer[j + 2] = Base64_MAP[((byteInput[i + 1] << 2) & 0b111100) | ((byteInput[i + 2] >> 6) & 0b11)];
      this.Buffer[j + 3] = Base64_MAP[byteInput[i + 2] & 0b111111];
    }

    if (len < length) {
      if (len + 2 > length) {
        this.Buffer[j++] = Base64_MAP[(byteInput[len] >> 2) & 0b111111];
        this.Buffer[j++] = Base64_MAP[((byteInput[i] << 4) & 0b110000)];
        this.Buffer[j++] = _ext3;
        this.Buffer[j++] = _ext3;
      }
      else {
        this.Buffer[j++] = Base64_MAP[(byteInput[len] >> 2) & 0b111111];
        this.Buffer[j++] = Base64_MAP[((byteInput[i] << 4) & 0b110000) | ((byteInput[i + 1] >> 4) & 0b1111)];
        this.Buffer[j++] = Base64_MAP[(byteInput[i + 1] << 2) & 0b111100];
        this.Buffer[j++] = _ext3;
      }
    }
    return j;
  }
}